page.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. import { notFound } from "next/navigation";
  2. import { Suspense } from "react";
  3. // import clsx from "clsx";
  4. import {
  5. ProductDetailSkeleton,
  6. RelatedProductSkeleton,
  7. } from "@/components/common/skeleton/ProductSkeleton";
  8. import {
  9. BASE_SCHEMA_URL,
  10. PRODUCT_TYPE,
  11. } from "@/utils/constants";
  12. import { GET_PRODUCT_BY_URL_KEY } from "@/graphql";
  13. import { cachedProductRequest } from "@/utils/hooks/useCache";
  14. import {
  15. ProductOption,
  16. SingleProductResponse,
  17. ProductMediaType,
  18. ProductFlexibleVariant,
  19. ResolvedVariant
  20. } from "@components/catalog/type";
  21. import { RelatedProductsSection } from "@components/catalog/product/RelatedProductsSection";
  22. import { HeroCarouselShimmer } from "@components/common/slider";
  23. import {ProductMedia} from "@/app/(public)/product/_components/ProductMedia";
  24. import { ProductInformation } from "@/app/(public)/product/_components/ProductInformation";
  25. import { ProductShortDescription } from "@/app/(public)/product/_components/ProductShortDescription";
  26. import { ProductDetail } from "@/app/(public)/product/_components/ProductDetail";
  27. import { ProductReviewSection } from "@/app/(public)/product/_components/ProductReviewSection";
  28. import {
  29. formatFlexibleVariants
  30. } from "@/utils/variantTools";
  31. // export const dynamic = 'auto'
  32. // // 'auto' | 'force-dynamic' | 'error' | 'force-static'
  33. async function getSingleProduct(urlKey: string) {
  34. try {
  35. const {data:dataById} = await cachedProductRequest<SingleProductResponse>(
  36. urlKey, // 产品名称
  37. GET_PRODUCT_BY_URL_KEY, // gql查询语句
  38. { urlKey: urlKey },
  39. );
  40. const product = dataById?.product || null;
  41. return product;
  42. } catch (error) {
  43. if (error instanceof Error) {
  44. console.error("Error fetching product:", {
  45. message: error.message,
  46. urlKey,
  47. graphQLErrors: (error as unknown as Record<string, unknown>)
  48. .graphQLErrors,
  49. });
  50. }
  51. return null;
  52. }
  53. }
  54. //动态路由中的参数 -- params
  55. export default async function ProductPage({
  56. params,
  57. }: {
  58. params: Promise<{ urlProduct: string[] }>;
  59. searchParams: Promise<{ type: string }>;
  60. }) {
  61. const { urlProduct } = await params;
  62. const fullPath = urlProduct.join("/");
  63. const product = await getSingleProduct(fullPath);
  64. if (!product) return notFound();
  65. // const imageUrl = getImageUrl(product?.baseImageUrl, baseUrl, NOT_IMAGE);
  66. // JSON-LD数据格式,便于搜索引擎识别页面信息,利于seo
  67. const productJsonLd = {
  68. "@context": BASE_SCHEMA_URL,
  69. "@type": PRODUCT_TYPE,
  70. name: product?.name,
  71. description: product?.description,
  72. sku: product?.sku,
  73. };
  74. const mediaImgs = (product?.images?.edges ?? []).map((edge: { node: ProductMediaType }) => {
  75. return edge.node;
  76. });
  77. const productOptions: ProductOption[] = product?.productOptions ? JSON.parse(product?.productOptions) : [];
  78. const flexibleVariants = formatFlexibleVariants(product?.flexibleVariants);
  79. const reviews = Array.isArray(product?.reviews?.edges)
  80. ? product?.reviews.edges.map((e) => e.node)
  81. : [];
  82. // const VariantImages = isArray(product?.variants?.edges)
  83. // ? product?.variants.edges.map(
  84. // (edge: { node: ProductVariantNode }) => edge.node,
  85. // )
  86. // : [];
  87. return (
  88. <>
  89. <script //SEO
  90. dangerouslySetInnerHTML={{
  91. __html: JSON.stringify(productJsonLd),
  92. }}
  93. type="application/ld+json"
  94. />
  95. <div className="w-full">
  96. <Suspense fallback={<HeroCarouselShimmer />}>
  97. <ProductMedia mediaData={mediaImgs} />
  98. </Suspense>
  99. </div>
  100. <div className="">
  101. <Suspense fallback={<ProductDetailSkeleton />}>
  102. <ProductInformation key={product._id}
  103. productId={product._id}
  104. name={product?.name || ''}
  105. productOptions={productOptions}
  106. flexibleVariants={flexibleVariants}
  107. isSaleable={product.isSaleable}
  108. />
  109. <ProductShortDescription shortDescription={product.shortDescription || ''} />
  110. <ProductDetail detail={product.description || ''} />
  111. <ProductReviewSection productId={String(product._id)} />
  112. </Suspense>
  113. </div>
  114. <Suspense fallback={<RelatedProductSkeleton />}>
  115. <RelatedProductsSection fullPath={fullPath} />
  116. </Suspense>
  117. </>
  118. );
  119. }